/*
pkao_util.cpp
2014/11/02
psycommando@gmail.com
Description:  Code for the kaomado utility
*/
#include "pkao_util.hpp"
#include "pmd2_filetypes.hpp"
#include "handystuff.hpp"
#include "library_wide.hpp"
#include "gfileutils.h"
#include "kao.hpp"
#include "tiled_image.hpp"
#include <sstream>
#include <iostream>
#include <string>
#include <fstream>
#include <cassert>
#include <Poco/Path.h>
#include <Poco/File.h>
#include <iomanip>
using namespace std;
using namespace pmd2;
using namespace utils;
using namespace g::fileutils;
using filetypes::CKaomado;

namespace pkao_util
{
//=================================================================================================
// Constants 
//=================================================================================================
    static const string EXE_NAME                            = "ppmd_kaoutil.exe";
    static const string PVERSION                            = "0.2";

    static const string DEFAULT_FACENAMES_FILENAME          = "facenames.txt";
    static const string DEFAULT_POKENAMES_FILENAME          = "pokenames.txt";
    static const string OPTION_SET_TOC_ENTRY_NAME_LIST      = "pn";
    static const string OPTION_SET_FACE_NAME_LIST           = "fn";
    static const string OPTION_SET_TOTAL_NB_ENTRIES_KAO_TOC = "n";
    static const string OPTION_SET_EXPORT_TO_RAW_IMG        = "raw";
    static const string OPTION_QUIET                        = "q";
    static const string OPTION_NON_ZEALOUS_STR_SEARCH       = "nz";


    //Definition of all the possible options for the program!
    static const array<optionparsing_t, 6> MY_OPTIONS  =
    {{
        //Disable console output except errors!
        {
            OPTION_QUIET,
            0,
            "Disable console output except errors!",
        },
        //Set a text file to read for naming exported folders!
        { 
            OPTION_SET_TOC_ENTRY_NAME_LIST,      
            1,
            "Set a text file to read for naming exported folders!",
        }, 
        //Set a text file to read for the name of each exported faces within a folder!
        { 
            OPTION_SET_FACE_NAME_LIST,           
            1,
            "Set a text file to read for the name of each exported faces within a folder!",
        }, 
        //Force the ToC to this size.(Def = 1,154)
        { 
            OPTION_SET_TOTAL_NB_ENTRIES_KAO_TOC,
            1,
            "Force the ToC to this size.(Def = " + to_string( filetypes::DEF_KAO_TOC_NB_ENTRIES ) + ")",
        }, 
        //Will output the images as a raw 4bpp tiled image, preceeded by a RGB24 palette!
        { 
            OPTION_SET_EXPORT_TO_RAW_IMG,        
            0,
            "Will output the images as a raw 4bpp tiled image, preceeded by a RGB24 palette!",
        },
        //Will make the program skip string search as often as possible
        {
            OPTION_NON_ZEALOUS_STR_SEARCH,
            0,
            "Disable zealous string search! Reduce compression efficiency, in favor of speed!",
        },
    }};

    //A little struct to make it easier to throw around any new parsed parameters !
    struct kao_params
    {
        Poco::Path     inputpath;
        string         outputpath; 
        vector<string> pokenames;
        vector<string> facenames;
        uint32_t       totalnbkaoentries;
        bool           bshouldWriteRawImg;
        bool           bisQuiet;
        bool           bIsZealous;
    };


//=================================================================================================
// Utility
//=================================================================================================
//
    vector<string> ReadStringListFile( const Poco::Path & filepath )
    {
        vector<string> stringlist;
        ifstream       strfile( filepath.toString() );

        if( !( strfile.good() && strfile.is_open() ) )
        {
            assert(false);
            std::stringstream strs;
            strs << "ReadStringListFile(): Error: stringlist file is missing or cannot be opened ! Path :\n"
                 << filepath.toString();
            throw runtime_error(strs.str());
        }

        while( !strfile.eof() && !strfile.bad() )
        {
            string tmp;
            getline( strfile, tmp );
            stringlist.push_back(tmp);
        }

        return std::move(stringlist);
    }


    void PrintUsage()
    {
	    cout << EXE_NAME <<" (option \"optionvalue\") \"inputpath\" \"outputpath\"\n"
             << "\n" 
             << "-> option          : An optional option from the list below..\n"
             << "-> optionvalue     : An optional value for the specified option..\n"
		     << "-> inputpath       : folder/file to pack/unpack.\n"
		     << "-> outputpath(opt) : folder/file to output the packed/unpacked file(s).\n"
             << "\n\n"
             << "Options:\n";
             //<< "      -" <<OPTION_SET_TOC_ENTRY_NAME_LIST 
             //          <<" \"filepath\" : Specifying this will make the program attempt to\n"
             //<< "                         read the specified file as a list of names to\n"
             //<< "                         give individual ToC entries(each folders).\n"
             //<< "                         Works only when unpacking!!"
             //<< "      -" <<OPTION_SET_FACE_NAME_LIST 
             //          <<" \"filepath\" : Specifying this will make the program attempt to\n"
             //<< "                         read the specified file as a list of names to\n"
             //<< "                         give each individual images for all ToC entries.\n"
             //<< "                         Works only when unpacking!!"
        //List our options
        for( auto & anoption : MY_OPTIONS )
            cout <<"    -" <<left  <<setw(5) <<setfill(' ') <<anoption.optionsymbol <<right <<" : " <<anoption.description <<"\n";

        cout << "\n"
		     << "Example:\n"
             << "---------\n"
             << EXE_NAME <<" ./kaomado.kao\n"
		     << EXE_NAME <<" ./kaomado.kao ./kaomado/\n"
             << EXE_NAME <<" -pn ./pokenames.txt ./kaomado.kao\n"
             << EXE_NAME <<" -fn ./facenames.txt ./kaomado.kao\n"
             << EXE_NAME <<" -pn ./pokenames.txt -fn ./facenames.txt ./kaomado.kao ./kaomado/\n"
             << "\n"
             << "To sum it up :\n"
             << "--------------\n"
             << "- If a file is passed as input path, the file will be unpacked.\n\n"
             << "\n"
             << "More details:\n"
             << "--------------\n"
             << "-> AS OF 2014/11/02 THE PACKING FEATURE IS NOT WORKING! It should be pretty soon though!\n"
             << "-> The 2 txt files used to specify the pokemon names and face names have a very simple structure.\n"
             << "   Just write one name per line, and the line number will correspond to the index value\n"
             << "   of the ToC entry or sub-entry in the Kaomado file, depending on the case!\n"
             << "\n"
             << "----------------------------------------------------------\n"
		     << "Named in honour of Baz, the awesome Poochyena of doom ! :D\n\n"
		     << "No crappyrights, all wrongs reversed !\n" 
             << "Sources and specs Included in original package!\n" <<endl;
    }


    bool HandleArguments( int argc, const char* argv[], kao_params& parameters )
    {
        CArgsParser parser( vector<optionparsing_t>( MY_OPTIONS.begin(), MY_OPTIONS.end() ), argv, argc );

        //We have 2 parameters max, at least one, and possibly one option
        string      paramOne       = parser.getNextParam(),
                    paramTwo       = parser.getNextParam();
        auto        validoptsfound = parser.getAllFoundOptions();
        string      facenamefile   = DEFAULT_FACENAMES_FILENAME,
                    pokenamefile   = DEFAULT_POKENAMES_FILENAME;
        bool        bsuccess       = false;

        //Poco::Path  inputfile,
        //            outputfile;

        if( !paramOne.empty() )
        {
            if( parameters.inputpath.tryParse(paramOne) )
            {

                //Parse first argument
                parameters.inputpath.makeAbsolute();

                //Check second argument !
                if( !paramTwo.empty()  )
                {
                    parameters.outputpath = paramTwo; //don't validate yet, we're doing it later
                }

                for( auto & anoption: validoptsfound )
                {
                    if(  anoption.size() == 2 ) //Only take options that have an argument parsed
                    {
                        if( anoption.front().compare(OPTION_SET_TOC_ENTRY_NAME_LIST) == 0 )
                        {
                            //Set toc entry names
                            pokenamefile = anoption.back();
                            if( !parameters.bisQuiet )
                            {
                                //parameters.pokenames = ReadStringListFile( anoption.back() );
                                cout <<"Option " <<OPTION_SET_TOC_ENTRY_NAME_LIST <<" specified. Using " 
                                        <<anoption.back() <<" as source for folder names!\n";
                            }
                        }
                        else if( anoption.front().compare(OPTION_SET_FACE_NAME_LIST) == 0 )
                        {
                            //Set faces names
                            facenamefile = anoption.back();
                            //parameters.facenames = ReadStringListFile( anoption.back() );
                            if( !parameters.bisQuiet )
                            {
                                cout <<"Option " <<OPTION_SET_FACE_NAME_LIST <<" specified. Using " 
                                        <<anoption.back() <<" as source for face names!\n";
                            }
                        }
                        else if( anoption.front().compare(OPTION_SET_TOTAL_NB_ENTRIES_KAO_TOC) == 0  )
                        {
                            //Overrides the default total amount of entries in the kaomado file
                            stringstream strs;
                            strs << anoption.back();
                            strs >> parameters.totalnbkaoentries;

                            if( !parameters.bisQuiet )
                            {
                                cout <<"Option " <<OPTION_SET_TOTAL_NB_ENTRIES_KAO_TOC <<" specified. Using " 
                                    <<parameters.totalnbkaoentries <<" as total nb of kaomado table entries!\n";
                            }
                        }
                    }
                    else if( anoption.size() == 1 ) //For any options with no parameters
                    {
                        if( anoption.front().compare(OPTION_SET_EXPORT_TO_RAW_IMG) == 0 )
                        {
                            //Overrides the default export to PNG, so it export to a raw tiled image instead
                            parameters.bshouldWriteRawImg = true;
                            if( !parameters.bisQuiet )
                            {
                                cout <<"Option " <<OPTION_SET_EXPORT_TO_RAW_IMG 
                                    <<" specified. Exporting to raw 16 color RGB palette + 4 bpp tiled image!\n";
                            }
                        }
                        else if( anoption.front().compare(OPTION_NON_ZEALOUS_STR_SEARCH) == 0 )
                        {
                            parameters.bIsZealous = false;
                            if( !parameters.bisQuiet )
                            {
                                cout <<"Option " <<OPTION_NON_ZEALOUS_STR_SEARCH 
                                    <<" specified. Disabling zealous string search!\n";
                            }
                        }
                        else if( anoption.front().compare(OPTION_QUIET) == 0 )
                        {
                            parameters.bisQuiet = true;
                        }
                    }
                    else
                    {
                        cerr << "<!>-WARNING: Invalid option \"" <<anoption.front() <<"\" detected!\n";
                    }
                }
                bsuccess = true;
            }
            else
            {
                cerr << "<!>-Fatal Error: Input file or path invalid!\n";
                bsuccess = false;
            }

            //Fill up facenames and pokenames 
            parameters.facenames = ReadStringListFile( facenamefile );
            parameters.pokenames = ReadStringListFile( pokenamefile );
        }

        return bsuccess;
    }

    //This determines what the output path should be considering the input path,
    // 
    Poco::Path PrepareOutputPath( bool isPacking, kao_params & parameters )
    {
        // check if output path empty
        if( !parameters.outputpath.empty() )
        {
            if( isPacking ) 
            {
                //Ouput path is file!
                Poco::Path outfile(parameters.outputpath);
                Poco::File testfile( Poco::Path(outfile).makeParent() );
                
                if( testfile.exists() && testfile.isDirectory() && !(outfile.getFileName().empty()) )
                    outfile.makeAbsolute();
                else
                    throw runtime_error("<!>-Fatal Error: Specified output path is invalid ! Aborting !");
                return std::move(outfile);
            }
            else 
            {
                //Output path is folder!
                Poco::Path outfolder(parameters.outputpath);
                Poco::File testfile(outfolder);

                if( testfile.exists() && testfile.isDirectory() )
                    outfolder.makeAbsolute();
                else
                    throw runtime_error("<!>-Fatal Error: Specified output path is invalid ! Aborting !");

                return std::move(outfolder);
            }
        }
        else
        {
            //Make our own path for packing  a folder to a file, and to unpack a file to a folder
            // input path was already validated
            if( isPacking )
            {
                //Output path is file!
                return Poco::Path(parameters.inputpath).makeFile().setExtension(filetypes::KAOMADO_FILEX);  //Turn into a filename, set the extension
            }
            else
            {
                //Output path is folder!
                return Poco::Path(parameters.inputpath).setFileName(parameters.inputpath.getBaseName()).makeDirectory(); //Remove extension, and turn into directory path
            }
        }
    }



//=================================================================================================
// Packing / Unpacking
//=================================================================================================

    bool DoUnpack( kao_params & parameters )
    {
        if( !parameters.bisQuiet )
            cout<<"Allocating..\n";
        Poco::Path       outpath = PrepareOutputPath( false, parameters );
        CKaomado         kao;
        types::bytevec_t filedata;

        if( !parameters.bisQuiet )
            cout<<"Reading file..\n";
        try
        {
            ReadFileToByteVector( parameters.inputpath.toString(), filedata );
        }
        catch( std::exception e ) 
        { 
            cerr << "DoUnpack(): Error reading the file: \n    \"" 
                <<parameters.inputpath.toString()
                <<"\"\n    " <<e.what() <<"\n";
            return false;
        }

        if( !parameters.bisQuiet )
        {
            cout << "\nUnpacking file : \n" 
                << "   " << parameters.inputpath.toString() <<"\n"
		        <<"into:\n" 
                << "   " <<outpath.toString() <<"\n" <<endl;
        }

        kao.ReadEntireKaomado( filedata.begin(), filedata.end(), parameters.bisQuiet );

        const vector<string> * ppokenames = ( (parameters.pokenames.empty() )? nullptr : &parameters.pokenames );
        const vector<string> * pfacenames = ( (parameters.facenames.empty() )? nullptr : &parameters.facenames );
        CKaomado::eEXPORT_t    outputTy   = ( (parameters.bshouldWriteRawImg)? CKaomado::eEXPORT_t::EX_RAW : 
                                                                               CKaomado::eEXPORT_t::EX_PNG );

        kao.ExportToFolders( outpath.toString(), ppokenames, pfacenames, outputTy, parameters.bisQuiet );

        if( !parameters.bisQuiet )
            cout << "Done! Deallocating..\n"; 

        return true;
    }

    bool DoPack( kao_params & parameters )
    {
        if( !parameters.bisQuiet )
            cout<<"Allocating..\n";
        CKaomado   kao(parameters.totalnbkaoentries);
        Poco::Path outpath = PrepareOutputPath( true, parameters );

        if( !parameters.bisQuiet )
        {
	        cout << "\nPacking Directory : \n"
                 << "   " <<parameters.inputpath.toString() <<"\n"
			     <<"into:\n" 
                 << "   " <<outpath.toString() <<"\n" <<endl;
        }

        kao.BuildFromFolder(parameters.inputpath.toString(), parameters.bisQuiet );
        types::bytevec_t filedata = std::move( kao.WriteKaomado( parameters.bisQuiet, parameters.bIsZealous ) );

        if( !parameters.bisQuiet )
            cout<<"Writing to file..\n";
        try
        {
            WriteByteVectorToFile( outpath.toString(), filedata );
        }
        catch( std::exception e ) 
        { 
            cerr << "DoPack(): Error writing the file: \n    \"" 
                <<parameters.inputpath.toString()
                <<"\"\n    " <<e.what() <<"\n";
            return false;
        }

        if( !parameters.bisQuiet )
            cout << "Done! Deallocating..\n"; 
        return true;
    }


//=================================================================================================
// Main Function
//=================================================================================================
    int RunPKaoUtil( int argc, const char * argv[] )
    {
        int        result     = 0;
        kao_params parameters = 
        { 
            Poco::Path(),                       //inputpath;
            string(),                           //outputpath; 
            vector<string>(),                   //pokenames;
            vector<string>(),                   //facenames;
            filetypes::DEF_KAO_TOC_NB_ENTRIES,  //totalnbkaoentries;
            false,                              //bshouldWriteRawImg;
            false,                              //bisQuiet
            true,                               //bIsZealous
        };

	    cout << "================================================\n"
             << "== Baz the Poochyena's PMD:EoS Kao util - " <<PVERSION <<" ==\n" 
             << "================================================\n"
             << "Utility for unpacking and building kaomado files!\n"
             << endl;

        //#1 - Get everything we need from the command line!
        if( !HandleArguments( argc, argv, parameters ) )
        {
            PrintUsage();
            return -1;
        }
        
        //#2 - Determine whether we're packing something, or unpacking something!
        {
            MrChronometer mychrono("Total");
            Poco::File    pathTester(parameters.inputpath);

            if( pathTester.exists() )
            {
                if( pathTester.isDirectory() )
                {
                    //We pack a folder
                    if( ! DoPack( parameters ) )
                    {
                        cerr << "<!>- Packing failed! Aborting !\n";
                        result = -1;
                    }
                }
                else
                {
                    //We unpack a file
                    if( ! DoUnpack( parameters ) )
                    {
                        cerr << "<!>- Unpacking failed! Aborting !\n";
                        result = -1;
                    }
                }
            }
            else
            {
                cerr << "<!>- FATAL ERROR: Input path argument is invalid ! Path doesn't exists!\n    Invalid Argument:\n    \""
                     << parameters.inputpath.toString() << "\"\nAborting !" 
                     <<endl;
                result = -1;
            }
        }

#ifdef _DEBUG
    #ifdef WIN32
	    system("pause");
   #elif  _linux_
        char a;
        std::cin >> a;
    #endif
#endif
        return result;
    }
};